/*-------------------------------------------------------------------------
 *
 * pg_enum.c
 *	  支持pg_enum关系操作的常规例程
 *
 * Copyright (c) 2006-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/catalog/pg_enum.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/value.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
#include "utils/syscache.h"

/* 可能由 pg_upgrade_support 函数设置 */
Oid			binary_upgrade_next_pg_enum_oid = InvalidOid;

/*
 * 在当前事务中通过AddEnumLabel创建的枚举值OID的哈希表。
 * 我们不允许在事务提交之前使用这些值；否则，它们可能会进入
 * 我们无法清理的索引中，如果事务回滚我们会有一个损坏的索引。
 * （参见enum.c中check_safe_enum_use()的注释。）由
 * EnumValuesCreate创建的值*不*被输入到表中；我们假设这些是在
 * CREATE TYPE期间创建的，所以除非枚举类型本身消失，否则这些值
 * 不能消失。
 */
static HTAB *uncommitted_enums = NULL;

static void fc_RenumberEnumType(Relation pg_enum, HeapTuple *fc_existing, int fc_nelems);
static int	sort_order_cmp(const void *fc_p1, const void *fc_p2);


/*
 * EnumValuesCreate
 *		为每个提供的枚举值在pg_enum中创建一个条目。
 *
 * vals是一个字符串值的列表。
 */
void EnumValuesCreate(Oid fc_enumTypeOid, List *fc_vals)
{
	Relation	pg_enum;
	NameData	fc_enumlabel;
	Oid		   *fc_oids;
	int			fc_elemno,
				fc_num_elems;
	Datum		fc_values[Natts_pg_enum];
	bool		fc_nulls[Natts_pg_enum];
	ListCell   *fc_lc;
	HeapTuple	fc_tup;

	fc_num_elems = list_length(fc_vals);

	/*
	 * 我们不 bother 检查值列表中的重复项 --- 如果你有任何重复，
	 * 你将会得到一个不友好的唯一索引冲突。可能没有必要更加努力。
	 */

	pg_enum = table_open(EnumRelationId, RowExclusiveLock);

	/*
	 * 为枚举的成员分配OID。
	 *
	 * 尽管此方法并不能绝对保证我们生成的OID没有重复（因为在分配下一个OID之前，我们并没有将每个OID录入表中），但只有在OID计数器完全回绕之前，我们完成操作时，才可能出现问题。这种情况似乎不太可能发生。
	 */
	fc_oids = (Oid *) palloc(fc_num_elems * sizeof(Oid));

	for (fc_elemno = 0; fc_elemno < fc_num_elems; fc_elemno++)
	{
		/*
		 * 我们将偶数OID分配给所有新的枚举标签。这
		 * 告诉比较函数OID的顺序是正确的，可以直接比较。
		 */
		Oid			fc_new_oid;

		do
		{
			fc_new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
										 Anum_pg_enum_oid);
		} while (fc_new_oid & 1);
		fc_oids[fc_elemno] = fc_new_oid;
	}

	/* 对它们进行排序，以防OID计数器从高到低回绕 */
	qsort(fc_oids, fc_num_elems, sizeof(Oid), oid_cmp);

	/* 并创建条目 */
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_elemno = 0;
	foreach(fc_lc, fc_vals)
	{
		char	   *fc_lab = strVal(lfirst(fc_lc));

		/*
		 * 标签存储在名称字段中，以方便进行系统缓存查找，
		 * 所以检查长度以确保在范围内。
		 */
		if (strlen(fc_lab) > (NAMEDATALEN - 1))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_NAME),
					 errmsg("invalid enum label \"%s\"", fc_lab),
					 errdetail("Labels must be %d bytes or less.",
							   NAMEDATALEN - 1)));

		fc_values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(fc_oids[fc_elemno]);
		fc_values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(fc_enumTypeOid);
		fc_values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(fc_elemno + 1);
		namestrcpy(&fc_enumlabel, fc_lab);
		fc_values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&fc_enumlabel);

		fc_tup = heap_form_tuple(RelationGetDescr(pg_enum), fc_values, fc_nulls);

		CatalogTupleInsert(pg_enum, fc_tup);
		heap_freetuple(fc_tup);

		fc_elemno++;
	}

	/* 清理 */
	pfree(fc_oids);
	table_close(pg_enum, RowExclusiveLock);
}


/*
 * EnumValuesDelete
 *		删除指定枚举类型的所有pg_enum条目。
 */
void EnumValuesDelete(Oid fc_enumTypeOid)
{
	Relation	pg_enum;
	ScanKeyData fc_key[1];
	SysScanDesc fc_scan;
	HeapTuple	fc_tup;

	pg_enum = table_open(EnumRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_enum_enumtypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_enumTypeOid));

	fc_scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
							  NULL, 1, fc_key);

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		CatalogTupleDelete(pg_enum, &fc_tup->t_self);
	}

	systable_endscan(fc_scan);

	table_close(pg_enum, RowExclusiveLock);
}

/*
 * 为此次事务初始化未提交的枚举表。
 */
static void fc_init_uncommitted_enums(void)
{
	HASHCTL		fc_hash_ctl;

	fc_hash_ctl.keysize = sizeof(Oid);
	fc_hash_ctl.entrysize = sizeof(Oid);
	fc_hash_ctl.hcxt = TopTransactionContext;
	uncommitted_enums = hash_create("Uncommitted enums",
									32,
									&fc_hash_ctl,
									HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
}

/*
 * AddEnumLabel
 *		向枚举集合添加新标签。默认情况下，它放在
 *		最后，但用户可以选择将其放在任何现有集合成员之前或之后。
 */
void AddEnumLabel(Oid fc_enumTypeOid,
			 const char *fc_newVal,
			 const char *fc_neighbor,
			 bool fc_newValIsAfter,
			 bool fc_skipIfExists)
{
	Relation	pg_enum;
	Oid			fc_newOid;
	Datum		fc_values[Natts_pg_enum];
	bool		fc_nulls[Natts_pg_enum];
	NameData	fc_enumlabel;
	HeapTuple	fc_enum_tup;
	float4		fc_newelemorder;
	HeapTuple  *fc_existing;
	CatCList   *fc_list;
	int			fc_nelems;
	int			fc_i;

	/* 检查新标签的长度是否合适 */
	if (strlen(fc_newVal) > (NAMEDATALEN - 1))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid enum label \"%s\"", fc_newVal),
				 errdetail("Labels must be %d bytes or less.",
						   NAMEDATALEN - 1)));

	/*
	 * 获取对枚举类型的锁，直到提交之前不会释放。
	 * 这确保了两个后端不会同时修改同一个
	 * 枚举类型。如果没有这一点，我们无法确保通过
	 * 系统缓存获得一致的枚举成员视图。注意，这不阻止
	 * 其他后端检查该类型；见
	 * RenumberEnumType的注释。
	 */
	LockDatabaseObject(TypeRelationId, fc_enumTypeOid, 0, ExclusiveLock);

	/*
	 * 检查标签是否已在使用中。pg_enum上的唯一索引会
	 * 捕获这一点，但我们更倾向于一个友好的错误消息，
	 * 此外，我们需要进行检查以支持IF NOT EXISTS。
	 */
	fc_enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
							   ObjectIdGetDatum(fc_enumTypeOid),
							   CStringGetDatum(fc_newVal));
	if (HeapTupleIsValid(fc_enum_tup))
	{
		ReleaseSysCache(fc_enum_tup);
		if (fc_skipIfExists)
		{
			ereport(NOTICE,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("enum label \"%s\" already exists, skipping",
							fc_newVal)));
			return;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("enum label \"%s\" already exists",
							fc_newVal)));
	}

	pg_enum = table_open(EnumRelationId, RowExclusiveLock);

	/* 如果我们必须重新编号现有成员，我们从这里重新开始 */
restart:

	/* 获取枚举的现有成员列表 */
	fc_list = SearchSysCacheList1(ENUMTYPOIDNAME,
							   ObjectIdGetDatum(fc_enumTypeOid));
	fc_nelems = fc_list->n_members;

	/* 按枚举排序顺序对现有成员进行排序 */
	fc_existing = (HeapTuple *) palloc(fc_nelems * sizeof(HeapTuple));
	for (fc_i = 0; fc_i < fc_nelems; fc_i++)
		fc_existing[fc_i] = &(fc_list->members[fc_i]->tuple);

	qsort(fc_existing, fc_nelems, sizeof(HeapTuple), sort_order_cmp);

	if (fc_neighbor == NULL)
	{
		/*
		 * 将新标签放在列表的末尾。无需修改现有
		 * 元组。
		 */
		if (fc_nelems > 0)
		{
			Form_pg_enum fc_en = (Form_pg_enum) GETSTRUCT(fc_existing[fc_nelems - 1]);

			fc_newelemorder = fc_en->enumsortorder + 1;
		}
		else
			fc_newelemorder = 1;
	}
	else
	{
		/* 指定了BEFORE或AFTER */
		int			fc_nbr_index;
		int			fc_other_nbr_index;
		Form_pg_enum fc_nbr_en;
		Form_pg_enum fc_other_nbr_en;

		/* 找到邻近元素 */
		for (fc_nbr_index = 0; fc_nbr_index < fc_nelems; fc_nbr_index++)
		{
			Form_pg_enum fc_en = (Form_pg_enum) GETSTRUCT(fc_existing[fc_nbr_index]);

			if (strcmp(NameStr(fc_en->enumlabel), fc_neighbor) == 0)
				break;
		}
		if (fc_nbr_index >= fc_nelems)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("\"%s\" is not an existing enum label",
							fc_neighbor)));
		fc_nbr_en = (Form_pg_enum) GETSTRUCT(fc_existing[fc_nbr_index]);

		/*
		 * 尝试分配一个合适的枚举排序顺序值：小于
		 * 最小成员的一个值，大于最大成员的一个值，或两个
		 * 现有成员之间的中间值。
		 *
		 * 在“中间”情况下，由于float4的有限精度，
		 * 我们可能会计算出一个实际上等于其邻居中某个值的数。
		 * 在这种情况下，我们将现有成员重新编号
		 * 并重新尝试。
		 */
		if (fc_newValIsAfter)
			fc_other_nbr_index = fc_nbr_index + 1;
		else
			fc_other_nbr_index = fc_nbr_index - 1;

		if (fc_other_nbr_index < 0)
			fc_newelemorder = fc_nbr_en->enumsortorder - 1;
		else if (fc_other_nbr_index >= fc_nelems)
			fc_newelemorder = fc_nbr_en->enumsortorder + 1;
		else
		{
			/*
			 * 此处计算得出的中点值必须四舍五入为float4
			 * 精度，否则我们与相邻值的相等比较将毫无意义。
			 * 强制这一点在不符合C标准的编译器中的最便携方式是将
			 * 它存储到一个易失性变量中。
			 */
			volatile float4 fc_midpoint;

			fc_other_nbr_en = (Form_pg_enum) GETSTRUCT(fc_existing[fc_other_nbr_index]);
			fc_midpoint = (fc_nbr_en->enumsortorder +
						fc_other_nbr_en->enumsortorder) / 2;

			if (fc_midpoint == fc_nbr_en->enumsortorder ||
				fc_midpoint == fc_other_nbr_en->enumsortorder)
			{
				fc_RenumberEnumType(pg_enum, fc_existing, fc_nelems);
				/* 清理并重新开始 */
				pfree(fc_existing);
				ReleaseCatCacheList(fc_list);
				goto restart;
			}

			fc_newelemorder = fc_midpoint;
		}
	}

	/* 为新标签获取新的OID */
	if (IsBinaryUpgrade)
	{
		if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("pg_enum OID value not set when in binary upgrade mode")));

		/*
		 * 如果提供，使用pg_enum.oid的二进制升级覆盖。在
		 * 二进制升级期间，所有pg_enum.oid都将以这种方式设置，以确保它们
		 * 一致。
		 */
		if (fc_neighbor != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));

		fc_newOid = binary_upgrade_next_pg_enum_oid;
		binary_upgrade_next_pg_enum_oid = InvalidOid;
	}
	else
	{
		/*
		 * 正常情况：我们需要为该值分配一个新的Oid。
		 *
		 * 如果安全，我们希望给新元素一个偶数Oid，
		 * 也就是说它与所有先前存在的偶数
		 * Oids在排序上是正确的。否则，我们必须给它一个奇数Oid。
		 */
		for (;;)
		{
			bool		fc_sorts_ok;

			/* 获取一个新的OID（与所有现有pg_enum元组不同） */
			fc_newOid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
										Anum_pg_enum_oid);

			/*
			 * 检测它是否相对现有的
			 * 偶数标签正确排序。我们可以忽略现有
			 * 奇数Oid的标签，因为涉及其中一个的比较
			 * 不会走快速路径。
			 */
			fc_sorts_ok = true;
			for (fc_i = 0; fc_i < fc_nelems; fc_i++)
			{
				HeapTuple	fc_exists_tup = fc_existing[fc_i];
				Form_pg_enum fc_exists_en = (Form_pg_enum) GETSTRUCT(fc_exists_tup);
				Oid			fc_exists_oid = fc_exists_en->oid;

				if (fc_exists_oid & 1)
					continue;	/* 忽略奇数Oid */

				if (fc_exists_en->enumsortorder < fc_newelemorder)
				{
					/* 应该排在前面 */
					if (fc_exists_oid >= fc_newOid)
					{
						fc_sorts_ok = false;
						break;
					}
				}
				else
				{
					/* 应该排在后面 */
					if (fc_exists_oid <= fc_newOid)
					{
						fc_sorts_ok = false;
						break;
					}
				}
			}

			if (fc_sorts_ok)
			{
				/* 如果它是偶数并且排序正确，我们就完成了。 */
				if ((fc_newOid & 1) == 0)
					break;

				/*
				 * 如果是奇数，并且排序正常，循环回去获取另一个 OID 并
				 * 再次尝试。实际上，下一个可用的偶数 OID 也可能排序
				 * 正常，因此值得一试。
				 */
			}
			else
			{
				/*
				 * 如果是奇数，并且排序不正确，我们就结束了。
				 * （实际上，下一个可用的偶数 OID 也很可能排序
				 * 不正确，因此没有必要再尝试。）
				 */
				if (fc_newOid & 1)
					break;

				/*
				 * 如果是偶数，并且排序不正确，循环回去获取
				 * 另一个 OID 并再试一次。（我们 *必须* 拒绝这种情况。）
				 */
			}
		}
	}

	/* 完成关于现有成员的信息 */
	pfree(fc_existing);
	ReleaseCatCacheList(fc_list);

	/* 创建新的 pg_enum 条目 */
	memset(fc_nulls, false, sizeof(fc_nulls));
	fc_values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(fc_newOid);
	fc_values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(fc_enumTypeOid);
	fc_values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(fc_newelemorder);
	namestrcpy(&fc_enumlabel, fc_newVal);
	fc_values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&fc_enumlabel);
	fc_enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), fc_values, fc_nulls);
	CatalogTupleInsert(pg_enum, fc_enum_tup);
	heap_freetuple(fc_enum_tup);

	table_close(pg_enum, RowExclusiveLock);

	/* 如果在此事务中尚未完成，则设置未提交的枚举表 */
	if (uncommitted_enums == NULL)
		fc_init_uncommitted_enums();

	/* 将新值添加到表中 */
	(void) hash_search(uncommitted_enums, &fc_newOid, HASH_ENTER, NULL);
}


/*
 * RenameEnumLabel
 *		重命名枚举集合中的标签。
 */
void RenameEnumLabel(Oid fc_enumTypeOid,
				const char *fc_oldVal,
				const char *fc_newVal)
{
	Relation	pg_enum;
	HeapTuple	fc_enum_tup;
	Form_pg_enum fc_en;
	CatCList   *fc_list;
	int			fc_nelems;
	HeapTuple	fc_old_tup;
	bool		fc_found_new;
	int			fc_i;

	/* 检查新标签的长度是否合适 */
	if (strlen(fc_newVal) > (NAMEDATALEN - 1))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("invalid enum label \"%s\"", fc_newVal),
				 errdetail("Labels must be %d bytes or less.",
						   NAMEDATALEN - 1)));

	/*
	 * 获取枚举类型的锁，直到提交才释放。
	 * 这确保了两个后端不会同时修改同一个
	 * 枚举类型。由于我们没有改变类型的排序顺序，这
	 * 可能不是特别必要，但没有理由不加锁以确保。
	 */
	LockDatabaseObject(TypeRelationId, fc_enumTypeOid, 0, ExclusiveLock);

	pg_enum = table_open(EnumRelationId, RowExclusiveLock);

	/* 获取枚举的现有成员列表 */
	fc_list = SearchSysCacheList1(ENUMTYPOIDNAME,
							   ObjectIdGetDatum(fc_enumTypeOid));
	fc_nelems = fc_list->n_members;

	/*
	 * 定位要重命名的元素并检查新标签是否已在
	 * 使用中。（pg_enum 上的唯一索引无论如何会捕获此情况，
	 * 但我们希望给出更友好的错误信息。）
	 */
	fc_old_tup = NULL;
	fc_found_new = false;
	for (fc_i = 0; fc_i < fc_nelems; fc_i++)
	{
		fc_enum_tup = &(fc_list->members[fc_i]->tuple);
		fc_en = (Form_pg_enum) GETSTRUCT(fc_enum_tup);
		if (strcmp(NameStr(fc_en->enumlabel), fc_oldVal) == 0)
			fc_old_tup = fc_enum_tup;
		if (strcmp(NameStr(fc_en->enumlabel), fc_newVal) == 0)
			fc_found_new = true;
	}
	if (!fc_old_tup)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("\"%s\" is not an existing enum label",
						fc_oldVal)));
	if (fc_found_new)
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("enum label \"%s\" already exists",
						fc_newVal)));

	/* 好的，制作旧元组的可写副本 */
	fc_enum_tup = heap_copytuple(fc_old_tup);
	fc_en = (Form_pg_enum) GETSTRUCT(fc_enum_tup);

	ReleaseCatCacheList(fc_list);

	/* 更新 pg_enum 条目 */
	namestrcpy(&fc_en->enumlabel, fc_newVal);
	CatalogTupleUpdate(pg_enum, &fc_enum_tup->t_self, fc_enum_tup);
	heap_freetuple(fc_enum_tup);

	table_close(pg_enum, RowExclusiveLock);
}


/*
 * 测试给定的枚举值是否在未提交的枚举表中。
 */
bool EnumUncommitted(Oid fc_enum_id)
{
	bool		fc_found;

	/* 如果我们没有制作未提交的表，所有值都是安全的 */
	if (uncommitted_enums == NULL)
		return false;

	/* 否则，它在表中吗？ */
	(void) hash_search(uncommitted_enums, &fc_enum_id, HASH_FIND, &fc_found);
	return fc_found;
}


/*
 * 在顶级事务结束后清理枚举相关内容。
 */
void AtEOXact_Enum(void)
{
	/*
	 * 重置未提交的表，因为我们所有的枚举值现在都已提交。
	 * 当 TopTransactionContext 释放时，内存将自动消失；
	 * 清除我们的指针就足够了。
	 */
	uncommitted_enums = NULL;
}


/*
 * RenumberEnumType
 *		重新编号现有枚举元素以使其排序位置为 1..n。
 *
 * 我们避免这样做，除非绝对必要；在大多数安装中，
 * 这不会发生。原因是更新现有的 pg_enum
 * 条目会给其他并发读取 pg_enum 的后端带来风险。
 * 尽管系统目录扫描现在使用 MVCC 语义，
 * 但 syscache 机制可能在不同快照下读取不同 pg_enum 条目，
 * 因此某些其他后端可能会对并发重新编号的适当顺序感到困惑。
 *
 * 因此，我们做出以下选择：
 *
 * 1. 任何对 enumsortorder 值感兴趣的代码必须以单个 MVCC 快照读取
 * 所有相关的 pg_enum 条目，或者获取枚举类型的锁，以防止 AddEnumLabel()
 * 的并发执行。
 *
 * 2. 不检查 enumsortorder 的代码可以使用 syscache
 * （例如，enum_in 和 enum_out 是这样做的）。
 */
static void fc_RenumberEnumType(Relation pg_enum, HeapTuple *fc_existing, int fc_nelems)
{
	int			fc_i;

	/*
	 * 我们应只需增加现有元素的 enumsortorders，
	 * 永远不要减少它们。因此，从末尾向前工作，以避免
	 * 不必要的唯一性冲突。
	 */
	for (fc_i = fc_nelems - 1; fc_i >= 0; fc_i--)
	{
		HeapTuple	fc_newtup;
		Form_pg_enum fc_en;
		float4		fc_newsortorder;

		fc_newtup = heap_copytuple(fc_existing[fc_i]);
		fc_en = (Form_pg_enum) GETSTRUCT(fc_newtup);

		fc_newsortorder = fc_i + 1;
		if (fc_en->enumsortorder != fc_newsortorder)
		{
			fc_en->enumsortorder = fc_newsortorder;

			CatalogTupleUpdate(pg_enum, &fc_newtup->t_self, fc_newtup);
		}

		heap_freetuple(fc_newtup);
	}

	/* 使更新可见 */
	CommandCounterIncrement();
}


/* 根据排序顺序对元组进行 qsort 比较函数 */
static int sort_order_cmp(const void *fc_p1, const void *fc_p2)
{
	HeapTuple	fc_v1 = *((const HeapTuple *) fc_p1);
	HeapTuple	fc_v2 = *((const HeapTuple *) fc_p2);
	Form_pg_enum fc_en1 = (Form_pg_enum) GETSTRUCT(fc_v1);
	Form_pg_enum fc_en2 = (Form_pg_enum) GETSTRUCT(fc_v2);

	if (fc_en1->enumsortorder < fc_en2->enumsortorder)
		return -1;
	else if (fc_en1->enumsortorder > fc_en2->enumsortorder)
		return 1;
	else
		return 0;
}

Size EstimateUncommittedEnumsSpace(void)
{
	size_t		fc_entries;

	if (uncommitted_enums)
		fc_entries = hash_get_num_entries(uncommitted_enums);
	else
		fc_entries = 0;

	/* 为终止符加一。 */
	return sizeof(Oid) * (fc_entries + 1);
}

void SerializeUncommittedEnums(void *fc_space, Size fc_size)
{
	Oid		   *fc_serialized = (Oid *) fc_space;

	/*
	 * 确保自调用者保留空间以来，哈希表的大小没有变化。
	 */
	Assert(fc_size == EstimateUncommittedEnumsSpace());

	/* 如果有哈希表，则将所有值写出。 */
	if (uncommitted_enums)
	{
		HASH_SEQ_STATUS fc_status;
		Oid		   *fc_value;

		hash_seq_init(&fc_status, uncommitted_enums);
		while ((fc_value = (Oid *) hash_seq_search(&fc_status)))
			*fc_serialized++ = *fc_value;
	}

	/* 写出终止符。 */
	*fc_serialized = InvalidOid;

	/*
	 * 确保我们实际使用的空间量与估算的空间量相符。
	 */
	Assert((char *) (fc_serialized + 1) == ((char *) fc_space) + fc_size);
}

void RestoreUncommittedEnums(void *fc_space)
{
	Oid		   *fc_serialized = (Oid *) fc_space;

	Assert(!uncommitted_enums);

	/*
	 * 作为特例，如果列表为空，则甚至不必
	 * 创建哈希表。这是通常的情况，因为枚举修改
	 * 预计会很少发生。
	 */
	if (!OidIsValid(*fc_serialized))
		return;

	/* 将所有值读取到新的哈希表中。 */
	fc_init_uncommitted_enums();
	do
	{
		hash_search(uncommitted_enums, fc_serialized++, HASH_ENTER, NULL);
	} while (OidIsValid(*fc_serialized));
}
